﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Net;
using System.Net.NetworkInformation;

namespace EkspSys.Network
{
    class TraceRoute
    {
        private BackgroundWorker worker = new BackgroundWorker();
        private IPAddress dest;

        public int PingTimeout = 2000;

        public delegate void RouterFoundHandler(IPAddress addr, Location loc);
        public delegate void TraceRouteCompletedHandler(IPAddress dest);
        public delegate void RouterFailedHandler();

        public event RouterFoundHandler RouterFoundEvent;
        public event RouterFailedHandler RouterFailedEvent;
        public event TraceRouteCompletedHandler TraceRouteCompletedEvent;

        public TraceRoute(IPAddress dest)
        {
            worker.WorkerReportsProgress = true;
            worker.ProgressChanged += ReportRouterFound;
            worker.DoWork += DoTrace;

            this.dest = dest;
        }

        public void BeginTrace()
        {
            worker.RunWorkerAsync();
        }

        private enum Status { ROUTE_FOUND, FAILED_ROUTE, COMPLETED }
        private class ProgressReport
        {
            public Status Status;

            public IPAddress Address;
            public Location AddressLocation;

            public ProgressReport(Status status)
            {
                this.Status = status;
            }

            public ProgressReport(Status status, IPAddress addr)
            {
                this.Status = status;
                this.Address = addr;
                this.AddressLocation = GeoLookup.Get().LookupIPLocation(addr);
            }
        }

        private void ReportRouterFound(object sender, ProgressChangedEventArgs e)
        {
            ProgressReport report = e.UserState as ProgressReport;
            switch (report.Status)
            {
                case Status.ROUTE_FOUND:
                    if (RouterFoundEvent != null)
                        RouterFoundEvent(report.Address, report.AddressLocation);
                    break;
                case Status.COMPLETED:
                    if (TraceRouteCompletedEvent != null)
                        TraceRouteCompletedEvent(report.Address);
                    break;
                case Status.FAILED_ROUTE:
                    if (RouterFailedEvent != null)
                        RouterFailedEvent();
                    break;
            }
        }

        private void DoTrace(object sender, DoWorkEventArgs e)
        {
            const int ttlLimit = 30;
            int ttl = 1;

            IPAddress lastRoute = null;
            while (ttl < ttlLimit && ttl != 0)
            {
                Ping ping = new Ping();
                PingOptions options = new PingOptions(ttl, true);
                PingReply reply = ping.Send(dest, PingTimeout, new byte[0], options);

                IPAddress route = reply.Address;
                if (reply.Status == IPStatus.TtlExpired)
                {
                    worker.ReportProgress(0, new ProgressReport(Status.ROUTE_FOUND, route));
                    lastRoute = route;
                    ttl++;
                }
                else if (reply.Status == IPStatus.Success)
                {
                    worker.ReportProgress(0, new ProgressReport(Status.ROUTE_FOUND, route));
                    lastRoute = route;
                    ttl = 0;
                }
                else
                {
                    // Failed!
                    worker.ReportProgress(0, new ProgressReport(Status.FAILED_ROUTE));
                    ttl++;
                }
            }

            worker.ReportProgress(0, new ProgressReport(Status.COMPLETED, lastRoute));
        }
    }
}
